package loader 
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.ProgressEvent;
	import flash.net.FileReference;
	import flash.net.URLRequest;
	import flash.system.ApplicationDomain;
	import flash.utils.Dictionary;
	
	/**
	 * Base ExternalLoader static class
	 * for load jpg,png,swf
	 * and load XML
	 * ...
	 * @author SChabanov
	 */
	public final class ExternalLoader
	{					
		
		public static const ALL_LOADED	   :String = "allLoaded";
		public static const GROUP_LOADED   :String = "groupLoaded";
		public static const RESOURCE_LOADED:String = "resourceLoaded";
		public static const TOTAL_PROGRESS :String = "totalProgress";
				
		private static var _dispatcher:EventDispatcher = new EventDispatcher();
		
		private static var _assets:Vector.<Asset> = new Vector.<Asset>();
					
		private static var _percentsTotal:Number = 0;
				
		private static var _groupCounter:int = 0;
		private static var _assetCounter:int = 0;
		private static var _index:int = 0;
		
		private static var _loadByDataStarted:Boolean;
		private static var _loadAllStarted:Boolean;
		private static var _loadByOneStarted:Boolean;
		private static var _maximumToLoad:int;
						
		private static var _lastLoadedResource:Asset = null;
		
		private static var _groupLoadStarted:Boolean;
		static private var _classNames:Dictionary;
		private static var _groupList:Vector.<Asset>;
		
		[Event (name='allLoaded',type='flash.events.Event')]
		[Event (name='resourceLoaded',type='flash.events.Event')]
		
		public function ExternalLoader() 
		{											
			throw new Error("static class");
		}
		
		/**
		 * 
		 * @param	groupName
		 * @return
		 */
		public static function getGroup(groupName:String=null):Vector.<Asset>
		{
			var group:Vector.<Asset> = _assets.filter(filterGroup);
			return group;
			function filterGroup(item:Asset, index:int, vec:Vector.<Asset>):Boolean { return item.groupName == groupName; };
		}
		/**
		 * 
		 * @param	sname
		 * @param	path
		 * @return
		 */
		public static function getAsset(sname:String,path:String=null):Asset
		{
			return getAssetByName(sname, path);
		}
				
		/**
		 * add many files to load
		 * @param	list
		 */
		public static function addFiles(list:Vector.<AssetData>):void
		{			
			for (var i:int = 0; i < list.length; i++) 
			{
				addFile(list[i].className, list[i].path, list[i].priority,list[i].groupName);
			}
		}
		
		/**
		 * add file to dictionary
		 * 
		 * @param	name
		 * @param	url
		 * @param	priority
		 * @return Asset instane
		 */			
		public static function addFile(name:String,url:String,priority:int=0,groupName:String=null):Asset
		{		
			var check:Asset = getAssetByName(name,url);
			if (check)
			{
				return check;
			}
			var asset:Asset = new Asset(name, url, priority, groupName);
			_assets.push(asset);
			
			return asset;	
		}
		
		/**
		 * Loads  file from url
		 * @param	url
		 * @param	onComplete
		 * @param	onProgress
		 */
		public static function loadFile(url:String,onComplete:Function,onProgress:Function = null):void
		{
			var fname:Array = new URLRequest(url).url.split("/");
			var a:Asset = new Asset(fname[fname.length-1], url);
			loadNext(a, onComplete, onProgress);
		}
		
		/**
		 * start loads all items by queue
		 * from dictionary
		 * 
		 */
		public static function load():void
		{
			if (_loadByOneStarted || _groupLoadStarted) return;
			if (_assets.filter(filterLoaded).length == 0) return;
			_index = 0;
			_maximumToLoad = _assets.length;
			_assets.sort(sortByPriority);
			loadNext(_assets[_index], onItemLoadedHandler);
			_loadByOneStarted = true;
			function filterLoaded(a:Asset,index:int,vector:Vector.<Asset>):Boolean{return !a.isLoaded}
		}		
		
		/**
		 * load assets by groupName
		 * load from dictionary
		 * @param	groupName
		 */	
		public static function loadGroup(groupName:String):void
		{
			if (_groupLoadStarted  || _loadByOneStarted) return;
			
			_groupCounter = 0;
			_index = 0;
			_groupList = _assets.filter(filterGroup);
			if (_groupList.length == 0 || _groupList.filter(filterLoaded).length == 0)
			{
				_groupList = null;
				return;
			}
			_groupList.sort(sortByPriority);
			_maximumToLoad = _groupList.length;
			loadNext(_groupList[_index],onGroupItemLoadHandler);
			_groupLoadStarted = true;
			function filterGroup(item:Asset, index:int, vec:Vector.<Asset>):Boolean { return item.groupName == groupName; };
			function filterLoaded(a:Asset, index:int, vector:Vector.<Asset>):Boolean { return !a.isLoaded };
		}
		
		/**
		 * load asset if is not exist add to dictionary
		 * @param	adata
		 * @param	onComplete is Event.COMPLETE handler
		 * @param	onProgress is ProgressEvent.PROGRESS handler
		 */
		public static function loadAsset(adata:AssetData,onCompleteHandler:Function = null,onProgressHandler:Function=null):void
		{
			if (adata == null) return;
			if (!isAssetExist(adata.className,adata.path)) addFile(adata.className, adata.path, adata.priority, adata.groupName);
			for (var i:int = 0; i < _assets.length; i++) 
			{
				if (_assets[i].name == adata.className || _assets[i].path == adata.path)
				{
					var complete:Function = onCompleteHandler == null ? 
											function(e:Event):void {
												_lastLoadedResource = e.currentTarget as Asset;
												_dispatcher.dispatchEvent(new Event(RESOURCE_LOADED)) 
											} : 
											onCompleteHandler;
					_lastLoadedResource = _assets[i];
					loadNext(_assets[i], complete , onProgressHandler);
					break;
				}
			}
			
		}
						
		/**
		 * 
		 * @param	type
		 * @param	listener
		 * @param	capture
		 * @param	priority
		 * @param	useWeakRef
		 */
		public static function addEventListener(type:String, listener:Function,capture:Boolean=false,priority:int=0,useWeakRef:Boolean=false):void
		{
			_dispatcher.addEventListener(type, listener, capture, priority, useWeakRef);
		}
		/**
		 * 
		 * @param	type
		 * @param	listener
		 * @param	capture
		 */
		public static function removeEventListener(type:String, listener:Function, capture:Boolean = false):void
		{
			_dispatcher.removeEventListener(type, listener, capture);
		}
		
		/**
		 * 
		 * @return
		 */
		public static function classNameExist(className:String):Boolean
		{
			if (_classNames == null)_classNames = new Dictionary(true);
			var result:Boolean = _classNames[className] != null;
			if (!result)
			{
				result = ApplicationDomain.currentDomain.hasDefinition(className)
				if (!result)
				{
						
						for (var i:int = 0; i < _assets.length; i++) 
						{
							if (_assets[i].resourceType == Asset.RESOURCE && _assets[i].isLoaded)
							{
								var cls:Class = _assets[i].linfo.applicationDomain.hasDefinition(className) ? 
												_assets[i].linfo.applicationDomain.getDefinition(className) as Class : null;
								if (cls != null)
								{
									_classNames[className] = cls;
									result = true;
								}
								break;
							}
						}
				}
			}
			return result;
		}
		/**
		 * 
		 * @param	className
		 * @return
		 */
		public static function getClass(className:String):Class
		{
			if (_classNames == null)_classNames = new Dictionary(true);
			var cls:Class = _classNames[className];
			if (cls == null)
			{
				
					cls = ApplicationDomain.currentDomain.hasDefinition(className) ? ApplicationDomain.currentDomain.getDefinition(className) as Class : null
					if (cls == null)
					{
						
						for (var i:int = 0; i < _assets.length; i++) 
						{
							if (_assets[i].resourceType == Asset.RESOURCE && _assets[i].isLoaded && _assets[i].linfo.applicationDomain.hasDefinition(className))
							{
								_classNames[className] = _assets[i].linfo.applicationDomain.getDefinition(className) as Class;
								break;
							}
						}
					}else {
						_classNames[className] = cls as Class;
					}
				
			}
			return _classNames[className];
			
			function filterLoaded(a:Asset, index:int, vector:Vector.<Asset>):Boolean {	return !a.isLoaded };
		}
		
		
		//---------------------------------------------------------------------------------------------------------------------------------//
		/**
		 * ##############################################################################################
		 * ########################################### @private #########################################
		 * ##############################################################################################
		 */
		
		//----------------------------------------------------------------------------------------------------------//
		/**
		 * 
		 * @param	sname
		 * @param	path
		 * @return
		 */
		private static function getAssetByName(sname:String,path:String):Asset
		{
			for (var i:int = 0; i < _assets.length; i++) 
			{
				if (_assets[i].path == path || _assets[i].name == sname)
				{
					return _assets[i];
				}
			}
			return null;
		}
		/**
		 * group loadItem handler
		 * @param	e
		 */
		private static function onGroupItemLoadHandler(e:Event):void 
		{
			_index++;
			_lastLoadedResource = e.currentTarget as Asset;
			_dispatcher.dispatchEvent(new Event(RESOURCE_LOADED));
			
			var assetPercent:Number = _lastLoadedResource.percentLoaded == 0 ? 100 : _lastLoadedResource.percentLoaded;
			//asset.removeEventListener(ProgressEvent.PROGRESS, onAssetLoadingHandler);
			
			_lastLoadedResource.removeEventListener(Event.COMPLETE, onGroupItemLoadHandler);
		
			_percentsTotal += assetPercent / _maximumToLoad;
			_dispatcher.dispatchEvent(new Event(TOTAL_PROGRESS));
			
			if (_index < _groupList.length)
			{			
				loadNext(_groupList[_index], onGroupItemLoadHandler);
			}else {
				_lastLoadedResource = null;
				_groupList.length = 0;
				_groupLoadStarted = false;
				_dispatcher.dispatchEvent(new Event(GROUP_LOADED));
				Logger.globalLog('group loaded')
				_percentsTotal = 0;
			}
			
		}
		
		/*private static function onAssetLoadedHandler(e:Event):void 
		{
			_assetCounter++;
			_loadedResource = e.currentTarget as Asset;
			_dispatcher.dispatchEvent(new Event(RESOURCE_LOADED));
			var assetPercent:Number = _loadedResource.percentLoaded == 0 ? 100 : _loadedResource.percentLoaded;
			//asset.removeEventListener(ProgressEvent.PROGRESS, onAssetLoadingHandler);
			_loadedResource.removeEventListener(Event.COMPLETE, onAssetLoadedHandler);
			_percentsTotal += Math.floor(assetPercent / _maximumToLoad)
			_dispatcher.dispatchEvent(new Event(TOTAL_PROGRESS));
		
			if (_assetCounter == _assets.length)
			{				
				_assetCounter = 0;
				trace('all loaded')
				_loadAllStarted = false;
				_loadedResource = null;
				_dispatcher.dispatchEvent(new Event(ALL_LOADED));
			}
		}*/
		/**
		 * dispatch RESOURCE_LOADED
		 * @param	e
		 */
		private static function onItemLoadedHandler(e:Event):void 
		{
			_index++;
			_lastLoadedResource = e.currentTarget as Asset;
			_dispatcher.dispatchEvent(new Event(RESOURCE_LOADED));
			
			
			var assetPercent:Number = _lastLoadedResource.percentLoaded == 0 ? 100 : _lastLoadedResource.percentLoaded;
			_lastLoadedResource.removeEventListener(Event.COMPLETE, onItemLoadedHandler);
			//asset.removeEventListener(ProgressEvent.PROGRESS, onItemLoadingHAndler);
			_percentsTotal += assetPercent / _maximumToLoad;
			_dispatcher.dispatchEvent(new Event(TOTAL_PROGRESS));
			
			if (_index < _assets.length)
			{				
				loadNext(_assets[_index], onItemLoadedHandler);
				
			}else {
				Logger.globalLog('all loaded')
				_lastLoadedResource = null;
				_dispatcher.dispatchEvent(new Event(ALL_LOADED));
				_loadByOneStarted = false;
				_percentsTotal = 0;
			}
			
		}
				
		/**
		 * simple load asset
		 * @param	a
		 * @param	onComplete (optional)
		 * @param	onProgress (optional)
		 */
		private static function loadNext(a:Asset,onComplete:Function=null,onProgress:Function=null):void
		{
			if (onComplete != null) a.addEventListener(Event.COMPLETE, onComplete, false, 0, true);
			if (onProgress != null) a.addEventListener(ProgressEvent.PROGRESS,onProgress, false, 0, true);
			a.start();
			
		}
		
		private static function isAssetExist(sname:String, path:String=null):Boolean
		{
			for (var i:int = 0; i < _assets.length; i++) 
			{
				if (_assets[i].name == sname || _assets[i].path == path)
				{
					return true;
				}
			}
			return false;
		}
		
		private static function sortByPriority(x:Asset,y:Asset):Number
		{
				if (x.priority > y.priority)
				{
					return -1;
				}else if (x.priority < y.priority)
				{
					return 1;
				}else {
					return 0;
				}
		}
		/**
		 * ACCESORS
		 */
		/**
		 * show total progress
		 */
		static public function get percentTotal():Number 
		{
			return _percentsTotal;
		}
		/**
		 * resource currently loaded 
		 * need check after RESOURCE_LOADED
		 */
		public static function get lastLoadedResource():Asset 
		{
			return _lastLoadedResource;
		}

	}

}

